/*
 * UAE - The Un*x Amiga Emulator
 *
 * Win32 sound interface (DirectSound)
 *
 * Copyright 1997 Mathias Ortmann
 * Copyright 1997-2001 Brian King
 * Copyright 2000-2002 Bernd Roesch
 * Copyright 2002-2003 Toni Wilen
 */

#include "sysconfig.h"
#include "gensound.h"
#include "options.h"
#include "audio.h"
#include "sound.h"
#include "savestate.h"
#include "gui.h"
#include "dxwrap.h"
#include "thread.h"
#include "newcpu.h"
#include "traps.h"
#include "driveclick.h"
#include "avioutput.h"
#include "custom.h"
#include "win32.h"

namespace Sound
{
    const int SOUND_FREQ = 44100;
}

struct sound_dp
{
    //  directsound
    // LPDIRECTSOUND8 lpDS;
    // LPDIRECTSOUNDBUFFER8 lpDSBsecondary;
    // DWORD writepos;
    // int dsoundbuf;
    // DWORD safedist;
    // int snd_writeoffset;
    // int snd_maxoffset;
    // int snd_totalmaxoffset_uf;
    // int snd_totalmaxoffset_of;
    // int max_sndbufsize;
    // int snd_configsize;

    //  openal
    // #define AL_BUFFERS 2
    // ALCdevice* al_dev;
    // ALCcontext* al_ctx;
    // ALuint al_Buffers[AL_BUFFERS];
    // ALuint al_Source;
    // int al_toggle;
    // DWORD al_format;
    // byte* al_bigbuffer;
    // int al_bufsize, al_offset;

    //  portaudio
    // volatile int patoggle;
    // volatile int pacounter;
    // byte* pasoundbuffer[2];
    // int pasndbufsize;
    // int paframesperbuffer;
    // PaStream* pastream;
    // HANDLE paevent;
    // int opacounter;
    // int pablocking;
    // int pavolume;

    //  wasapi
    IMMDevice* pDevice;
    IAudioClient* pAudioClient;
    IAudioRenderClient* pRenderClient;
    ISimpleAudioVolume* pAudioVolume;
    IMMDeviceEnumerator* pEnumerator;
    #if 0
    IAudioClock* pAudioClock;
    UINT64 wasapiclock;
    #endif
    REFERENCE_TIME hnsRequestedDuration;
    UINT32 bufferFrameCount;
    UINT64 wasapiframes;
    // int wasapiexclusive;
    int framecounter;
    int sndbuf;
    int wasapigoodsize;
    int sndbufframes;
};

#define ADJUST_SIZE 30
#define EXP 2.1

#define ADJUST_VSSIZE 10
// #define EXPVS 1.3

// int sound_debug = 0;
// int sound_mode_skip = 0;
// int sounddrivermask;

static int have_sound;
static int statuscnt;

#define SND_MAX_BUFFER2 524288
#define SND_MAX_BUFFER 8192

ushort paula_sndbuffer[SND_MAX_BUFFER];
ushort* paula_sndbufpt;
int paula_sndbufsize;

static uae_sem_t sound_sem, sound_init_sem;

// struct sound_device sound_devices[MAX_SOUND_DEVICES];
struct sound_device record_devices[MAX_SOUND_DEVICES];
/*static*/ int /*num_sound_devices,*/ num_record_devices = 0;

static struct sound_data sdpaula;
static struct sound_data* sdp = &sdpaula;

int setup_sound()
{
    sound_available = 1;
    return 1;
}

static int isvsync()
{
    return g_curr_conf.gfx_avsync && g_curr_conf.gfx_afullscreen == GFX_FULLSCREEN;
}

float scaled_sample_evtime_orig;
extern float sampler_evtime;
void update_sound(double freq, int longframe, int linetoggle)
{
    static double lastfreq;
    double lines = 0;
    double hpos;

    if (freq < 0)
        freq = lastfreq;
    lastfreq = freq;

    if (!have_sound)
        return;

    if (linetoggle)
    {
        hpos = maxhpos_short + 0.5;
        lines += 0.5;
    }
    else
    {
        if (longframe < 0)
            lines += 0.5;
        else if (longframe > 0)
            lines += 1.0;
        hpos = maxhpos_short;
    }
    lines += maxvpos_nom;
    scaled_sample_evtime_orig = hpos * lines * freq * CYCLE_UNIT / (double)sdp->obtainedfreq;
    scaled_sample_evtime = scaled_sample_evtime_orig;
    sampler_evtime = hpos * lines * freq * CYCLE_UNIT;
    #if 0
    lines -= maxvpos_nom;
    Logger::Write(L"%d.%d %d.%d %.2f\n",
        maxhpos_short, linetoggle ? 5 : 0,
        maxvpos_nom + (lines == 1.0 ? 1 : 0), lines > 0 && lines < 1 ? 5 : 0,
        scaled_sample_evtime);
    #endif
}

// static void clearbuffer_ds(struct sound_data* sd)
// {
//     void* buffer;
//     DWORD size;
//     struct sound_dp* s = sd->data;
//
//     HRESULT hr = IDirectSoundBuffer_Lock(s->lpDSBsecondary, 0, s->dsoundbuf, &buffer, &size, nullptr, nullptr, 0);
//     if (hr == DSERR_BUFFERLOST)
//     {
//         IDirectSoundBuffer_Restore(s->lpDSBsecondary);
//         hr = IDirectSoundBuffer_Lock(s->lpDSBsecondary, 0, s->dsoundbuf, &buffer, &size, nullptr, nullptr, 0);
//     }
//     if (FAILED(hr))
//     {
//         Logger::Write(L"DSSOUND: failed to Lock sound buffer (clear): %s\n", DXError(hr));
//         return;
//     }
//     memset(buffer, 0, size);
//     IDirectSoundBuffer_Unlock(s->lpDSBsecondary, buffer, size, nullptr, 0);
// }
//
// static void clearbuffer(struct sound_data* sd)
// {
//     if (sd->devicetype == SOUND_DEVICE_DS)
//         clearbuffer_ds(sd);
// }

static void pause_audio_wasapi(struct sound_data* sd)
{
    struct sound_dp* s = sd->data;
    HRESULT hr;

    hr = s->pAudioClient->Stop();
    if (FAILED(hr))
        Logger::Write(L"WASAPI: Stop() %08X\n", hr);
}
static void resume_audio_wasapi(struct sound_data* sd)
{
    struct sound_dp* s = sd->data;
    HRESULT hr;
    BYTE* pData;
    int framecnt;

    hr = s->pAudioClient->Reset();
    if (FAILED(hr))
        Logger::Write(L"WASAPI: Reset() %08X\n", hr);
    framecnt = s->wasapigoodsize;
    hr = s->pRenderClient->GetBuffer(framecnt, &pData);
    if (FAILED(hr))
        return;
    hr = s->pRenderClient->ReleaseBuffer(framecnt, AUDCLNT_BUFFERFLAGS_SILENT);
    hr = s->pAudioClient->Start();
    if (FAILED(hr))
        Logger::Write(L"WASAPI: Start() %08X\n", hr);
    s->wasapiframes = 0;
    s->framecounter = 0;
    s->sndbuf = 0;
}

// static void pause_audio_ds(struct sound_data* sd)
// {
//     struct sound_dp* s = sd->data;
//     HRESULT hr;
//
//     sd->waiting_for_buffer = 0;
//     hr = IDirectSoundBuffer_Stop(s->lpDSBsecondary);
//     if (FAILED(hr))
//         Logger::Write(L"DSSOUND: DirectSoundBuffer_Stop failed, %s\n", DXError(hr));
//     hr = IDirectSoundBuffer_SetCurrentPosition(s->lpDSBsecondary, 0);
//     if (FAILED(hr))
//         Logger::Write(L"DSSOUND: DirectSoundBuffer_SetCurretPosition failed, %s\n", DXError(hr));
//     clearbuffer(sd);
// }
//
// static void resume_audio_ds(struct sound_data* sd)
// {
//     sd->paused = 0;
//     clearbuffer(sd);
//     sd->waiting_for_buffer = 1;
// }
//
// static void pause_audio_pa(struct sound_data* sd)
// {
//     struct sound_dp* s = sd->data;
//     PaError err = Pa_StopStream(s->pastream);
//     if (err != paNoError)
//         Logger::Write(L"PASOUND: Pa_StopStream() error %d (%s)\n", err, Pa_GetErrorText(err));
// }
//
// static void resume_audio_pa(struct sound_data* sd)
// {
//     struct sound_dp* s = sd->data;
//     PaError err = Pa_StartStream(s->pastream);
//     if (err != paNoError)
//         Logger::Write(L"PASOUND: Pa_StartStream() error %d (%s)\n", err, Pa_GetErrorText(err));
//     sd->paused = 0;
// }
//
// static void pause_audio_al(struct sound_data* sd)
// {
//     struct sound_dp* s = sd->data;
//     sd->waiting_for_buffer = 0;
//     alSourcePause(s->al_Source);
// }
//
// static void resume_audio_al(struct sound_data* sd)
// {
//     struct sound_dp* s = sd->data;
//     sd->waiting_for_buffer = 1;
//     s->al_offset = 0;
// }
//
// static int restore_ds(struct sound_data* sd, DWORD hr)
// {
//     struct sound_dp* s = sd->data;
//     if (hr != DSERR_BUFFERLOST)
//         return 0;
//     if (sound_debug)
//         Logger::Write(L"DSSOUND: sound buffer lost\n");
//     hr = IDirectSoundBuffer_Restore(s->lpDSBsecondary);
//     if (FAILED(hr))
//     {
//         Logger::Write(L"DSSOUND: restore failed %s\n", DXError(hr));
//         return 1;
//     }
//     pause_audio_ds(sd);
//     resume_audio_ds(sd);
//     return 1;
// }
//
// static LARGE_INTEGER qpfc, qpf;
//
// static void storeqpf()
// {
//     QueryPerformanceCounter(&qpfc);
// }
//
// static double getqpf()
// {
//     LARGE_INTEGER qpfc2;
//     QueryPerformanceCounter(&qpfc2);
//     return (qpfc2.QuadPart - qpfc.QuadPart) / (qpf.QuadPart / 1000.0);
// }
//
// static void close_audio_ds(struct sound_data* sd)
// {
//     struct sound_dp* s = sd->data;
//     if (s->lpDSBsecondary)
//         IDirectSound_Release(s->lpDSBsecondary);
//     s->lpDSBsecondary = 0;
//     #ifdef USE_PRIMARY_BUFFER
//     if (s->lpDSBprimary)
//         IDirectSound_Release(s->lpDSBprimary);
//     s->lpDSBprimary = 0;
//     #endif
//     if (s->lpDS)
//     {
//         IDirectSound_Release(s->lpDS);
//         Logger::Write(L"DSSOUND: DirectSound driver freed\n");
//     }
//     s->lpDS = 0;
// }
//
// extern HWND hMainWnd;
// extern void setvolume_ahi(LONG);

void set_volume_sound_device(struct sound_data* sd, int volume, int mute)
{
    struct sound_dp* s = sd->data;
    HRESULT hr;

    // if (sd->devicetype == SOUND_DEVICE_AL)
    // {
    //     float vol = 0.0;
    //     if (volume < 100 && !mute)
    //         vol = (100 - volume) / 100.0;
    //     alSourcef(s->al_Source, AL_GAIN, vol);
    // }
    // else if (sd->devicetype == SOUND_DEVICE_DS)
    // {
    //     LONG vol = DSBVOLUME_MIN;
    //     if (volume < 100 && !mute)
    //         vol = (LONG)((DSBVOLUME_MIN / 2) + (-DSBVOLUME_MIN / 2) * log(1 + (2.718281828 - 1) * (1 - volume / 100.0)));
    //     hr = IDirectSoundBuffer_SetVolume(s->lpDSBsecondary, vol);
    //     if (FAILED(hr))
    //         Logger::Write(L"DSSOUND: SetVolume(%d) failed: %s\n", vol, DXError(hr));
    // }
    // else if (sd->devicetype == SOUND_DEVICE_WASAPI)
    // {
    if (s->pAudioVolume)
    {
        float vol = 0.0;
        if (volume < 100 && !mute)
            vol = (100 - volume) / 100.0;
        hr = s->pAudioVolume->SetMasterVolume(vol, nullptr);
        if (FAILED(hr))
            Logger::Write(L"AudioVolume->SetMasterVolume(%.2f) failed: %08Xs\n", vol, hr);
        hr = s->pAudioVolume->SetMute(mute, nullptr);
        if (FAILED(hr))
            Logger::Write(L"pAudioVolume->SetMute(%d) failed: %08Xs\n", mute, hr);
    }
    // }
    // else if (sd->devicetype == SOUND_DEVICE_PA)
    // {
    //     s->pavolume = volume;
    // }
}

void set_volume(int volume, int mute)
{
    set_volume_sound_device(sdp, volume, mute);
    // setvolume_ahi(volume);
    config_changed = 1;
}

// static void recalc_offsets(struct sound_data* sd)
// {
//     struct sound_dp* s = sd->data;
//     s->snd_writeoffset = s->max_sndbufsize * 5 / 8;
//     s->snd_maxoffset = s->max_sndbufsize;
//     s->snd_totalmaxoffset_of = s->max_sndbufsize + (s->dsoundbuf - s->max_sndbufsize) * 3 / 9;
//     s->snd_totalmaxoffset_uf = s->max_sndbufsize + (s->dsoundbuf - s->max_sndbufsize) * 7 / 9;
// }
//
// //const static GUID KSDATAFORMAT_SUBTYPE_PCM = {0x00000001,0x0000,0x0010,
// //{0x80,0x00,0x00,0xaa,0x00,0x38,0x9b,0x71}};
// #define KSAUDIO_SPEAKER_QUAD_SURROUND (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | \
//  // // //                                       SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT)
//
// #define MAX_SUPPORTEDMODES 16
// static struct dsaudiomodes supportedmodes[MAX_SUPPORTEDMODES];
//
// static DWORD fillsupportedmodes(struct sound_data* sd, int freq, struct dsaudiomodes* dsam)
// {
//     static DWORD speakerconfig;
//     DSBUFFERDESC sound_buffer;
//     WAVEFORMATEXTENSIBLE wavfmt;
//     LPDIRECTSOUNDBUFFER pdsb;
//     HRESULT hr;
//     int ch, round, mode, skip;
//     DWORD rn[4];
//     struct sound_dp* s = sd->data;
//     LPDIRECTSOUND8 lpDS = s->lpDS;
//     static int done;
//
//     if (done)
//         return speakerconfig;
//
//     mode = 2;
//     dsam[0].ch = 1;
//     dsam[0].ksmode = 0;
//     dsam[1].ch = 2;
//     dsam[1].ksmode = 0;
//     if (FAILED(IDirectSound8_GetSpeakerConfig(lpDS, &speakerconfig)))
//         speakerconfig = DSSPEAKER_STEREO;
//
//     memset(&wavfmt, 0, sizeof(WAVEFORMATEXTENSIBLE));
//     wavfmt.Format.nSamplesPerSec = freq;
//     wavfmt.Format.wBitsPerSample = 16;
//     wavfmt.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
//     wavfmt.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
//     wavfmt.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
//     wavfmt.Samples.wValidBitsPerSample = 16;
//     for (ch = 4; ch <= 6; ch += 2)
//     {
//         wavfmt.Format.nChannels = ch;
//         wavfmt.Format.nBlockAlign = wavfmt.Format.wBitsPerSample / 8 * wavfmt.Format.nChannels;
//         wavfmt.Format.nAvgBytesPerSec = wavfmt.Format.nBlockAlign * wavfmt.Format.nSamplesPerSec;
//         if (ch == 6)
//         {
//             rn[0] = KSAUDIO_SPEAKER_5POINT1;
//             rn[1] = KSAUDIO_SPEAKER_5POINT1_SURROUND;
//             rn[2] = 0;
//         }
//         else
//         {
//             rn[0] = KSAUDIO_SPEAKER_QUAD;
//             rn[1] = KSAUDIO_SPEAKER_QUAD_SURROUND;
//             rn[2] = KSAUDIO_SPEAKER_SURROUND;
//             rn[3] = 0;
//         }
//         skip = sound_mode_skip;
//         for (round = 0; rn[round]; round++)
//         {
//             if (skip > 0 && rn[round + 1] != 0)
//             {
//                 skip--;
//                 continue;
//             }
//             wavfmt.dwChannelMask = rn[round];
//             memset(&sound_buffer, 0, sizeof(sound_buffer));
//             sound_buffer.dwSize = sizeof(sound_buffer);
//             sound_buffer.dwBufferBytes = s->dsoundbuf;
//             sound_buffer.lpwfxFormat = &wavfmt.Format;
//             sound_buffer.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS;
//             sound_buffer.dwFlags |= DSBCAPS_CTRLVOLUME;
//             sound_buffer.guid3DAlgorithm = GUID_NULL;
//             pdsb = nullptr;
//             hr = IDirectSound_CreateSoundBuffer(lpDS, &sound_buffer, &pdsb, nullptr);
//             if (SUCCEEDED(hr))
//             {
//                 IDirectSound_Release(pdsb);
//                 dsam[mode].ksmode = rn[round];
//                 dsam[mode].ch = ch;
//                 mode++;
//                 if (mode >= MAX_SUPPORTEDMODES - 1)
//                     break;
//             }
//         }
//     }
//     dsam[mode].ch = 0;
//     dsam[mode].ksmode = 0;
//     done = 1;
//     return speakerconfig;
// }
//
//
// static void finish_sound_buffer_pa(struct sound_data* sd, ushort* sndbuffer)
// {
//     struct sound_dp* s = sd->data;
//     if (s->pavolume)
//     {
//         int vol = 65536 - s->pavolume * 655;
//         for (int i = 0; i < sd->sndbufsize / sizeof(ushort); i++)
//         {
//             short v = (short)sndbuffer[i];
//             sndbuffer[i] = v * vol / 65536;
//         }
//     }
//     if (s->pablocking)
//     {
//         if (s->paframesperbuffer != sd->sndbufsize / (sd->channels * 2))
//         {
//             Logger::Write(L"sound buffer size mistmatch %d <> %d\n", s->paframesperbuffer, sd->sndbufsize / (sd->channels * 2));
//         }
//         else
//         {
//             Pa_WriteStream(s->pastream, sndbuffer, s->paframesperbuffer);
//         }
//     }
//     else
//     {
//         int cnt = 2000 / 10;
//         while (s->opacounter == s->pacounter && s->pastream && !sd->paused && cnt-- >= 0)
//             WaitForSingleObject(s->paevent, 10);
//         s->opacounter = s->pacounter;
//         memcpy(s->pasoundbuffer[s->patoggle], sndbuffer, sd->sndbufsize);
//     }
// }
//
// static int _cdecl portAudioCallback(const void* inputBuffer, void* outputBuffer,
//                                     ulong framesPerBuffer,
//                                     const PaStreamCallbackTimeInfo* timeInfo,
//                                     PaStreamCallbackFlags statusFlags,
//                                     void* userData)
// {
//     struct sound_data* sd = (struct sound_data*)userData;
//     struct sound_dp* s = sd->data;
//
//     if (framesPerBuffer != sd->sndbufsize / (sd->channels * 2))
//     {
//         Logger::Write(L"sound buffer size mistmatch %d <> %d\n", framesPerBuffer, sd->sndbufsize / (sd->channels * 2));
//     }
//     else
//     {
//         memcpy(outputBuffer, s->pasoundbuffer[s->patoggle], sd->sndbufsize);
//     }
//     s->patoggle ^= 1;
//     s->pacounter++;
//     SetEvent(s->paevent);
//     return paContinue;
// }
//
// static void close_audio_pa(struct sound_data* sd)
// {
//     struct sound_dp* s = sd->data;
//     int i;
//
//     if (s->pastream)
//         Pa_CloseStream(s->pastream);
//     s->pastream = nullptr;
//     for (i = 0; i < 2; i++)
//     {
//         free(s->pasoundbuffer[i]);
//         s->pasoundbuffer[i] = nullptr;
//     }
//     if (s->paevent)
//     {
//         SetEvent(s->paevent);
//         CloseHandle(s->paevent);
//     }
//     s->paevent = nullptr;
// }
//
// static int open_audio_pa(struct sound_data* sd, int index)
// {
//     struct sound_dp* s = sd->data;
//     int i;
//     int freq = sd->freq;
//     int ch = sd->channels;
//     int size;
//     int dev = sound_devices[index].panum;
//     const PaDeviceInfo* di;
//     PaStreamParameters p;
//     PaError err;
//     TCHAR* name;
//     TCHAR* errtxt;
//     int defaultrate = 0;
//
//     size = sd->sndbufsize;
//     s->paframesperbuffer = size;
//     sd->devicetype = SOUND_DEVICE_PA;
//     memset(&p, 0, sizeof p);
//     di = Pa_GetDeviceInfo(dev);
//     for (;;)
//     {
//         int err2;
//         p.channelCount = ch;
//         p.device = dev;
//         p.hostApiSpecificStreamInfo = nullptr;
//         p.sampleFormat = paInt16;
//         p.suggestedLatency = di->defaultLowOutputLatency;
//         p.hostApiSpecificStreamInfo = nullptr;
//
//         err = Pa_IsFormatSupported(nullptr, &p, freq);
//         if (err == paFormatIsSupported)
//             break;
//         err2 = err;
//         errtxt = Unicode::au(Pa_GetErrorText(err));
//         Logger::Write(L"PASOUND: sound format not supported, ch=%d, rate=%d. %s\n", freq, ch, errtxt);
//         free(errtxt);
//         if (err == paInvalidChannelCount)
//         {
//             if (ch > 2)
//             {
//                 ch = sd->channels = 2;
//                 continue;
//             }
//             goto end;
//         }
//         if (freq < 44000 && err == paInvalidSampleRate)
//         {
//             freq = 44000;
//             sd->freq = freq;
//             continue;
//         }
//         if (freq < 48000 && err == paInvalidSampleRate)
//         {
//             freq = 48000;
//             sd->freq = freq;
//             continue;
//         }
//         if (freq != di->defaultSampleRate && err == paInvalidSampleRate && !defaultrate)
//         {
//             freq = di->defaultSampleRate;
//             sd->freq = freq;
//             defaultrate = 1;
//             continue;
//         }
//         goto end;
//     }
//     sd->sndbufsize = size * ch * 2;
//     //    s->pablocking = 1;
//     //    err = Pa_OpenStream (&s->pastream, nullptr, &p, freq, s->paframesperbuffer, paNoFlag, nullptr, nullptr);
//     //    if (err != paNoError) {
//     s->pablocking = 0;
//     err = Pa_OpenStream(&s->pastream, nullptr, &p, freq, s->paframesperbuffer, paNoFlag, portAudioCallback, sd);
//     if (err != paNoError)
//     {
//         errtxt = Unicode::au(Pa_GetErrorText(err));
//         Logger::Write(L"PASOUND: Pa_OpenStream() error %d (%s)\n", err, errtxt);
//         free(errtxt);
//         goto end;
//     }
//     //    }
//     s->paevent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
//     for (i = 0; i < 2; i++)
//         s->pasoundbuffer[i] = xcalloc(byte, sd->sndbufsize);
//     name = Unicode::au(di->name);
//     Logger::Write(L"PASOUND: CH=%d,FREQ=%d (%s) '%s' buffer %d\n",
//               ch, freq, sound_devices[index].name, name, sd->sndbufsize);
//     free(name);
//     return 1;
// end:
//     s->pastream = nullptr;
//     close_audio_pa(sd);
//     return 0;
// }
//
// static void close_audio_al(struct sound_data* sd)
// {
//     struct sound_dp* s = sd->data;
//     int i;
//
//     alDeleteSources(1, &s->al_Source);
//     s->al_Source = 0;
//     alDeleteBuffers(AL_BUFFERS, s->al_Buffers);
//     alcMakeContextCurrent(nullptr);
//     if (s->al_ctx)
//         alcDestroyContext(s->al_ctx);
//     s->al_ctx = nullptr;
//     if (s->al_dev)
//         alcCloseDevice(s->al_dev);
//     s->al_dev = nullptr;
//     for (i = 0; i < AL_BUFFERS; i++)
//     {
//         s->al_Buffers[i] = 0;
//     }
//     free(s->al_bigbuffer);
//     s->al_bigbuffer = nullptr;
// }
//
// static int open_audio_al(struct sound_data* sd, int index)
// {
//     struct sound_dp* s = sd->data;
//     int freq = sd->freq;
//     int ch = sd->channels;
//     char* name;
//     int size;
//
//     size = sd->sndbufsize;
//     sd->devicetype = SOUND_DEVICE_AL;
//     size *= ch * 2;
//     sd->sndbufsize = size / 8;
//     if (sd->sndbufsize > SND_MAX_BUFFER)
//         sd->sndbufsize = SND_MAX_BUFFER;
//     s->al_bufsize = size;
//     s->al_bigbuffer = xcalloc(byte, s->al_bufsize);
//     name = Unicode::ua(sound_devices[index].alname);
//     s->al_dev = alcOpenDevice(name);
//     free(name);
//     if (!s->al_dev)
//         goto error;
//     s->al_ctx = alcCreateContext(s->al_dev, nullptr);
//     if (!s->al_ctx)
//         goto error;
//     alcMakeContextCurrent(s->al_ctx);
//     alGenBuffers(AL_BUFFERS, s->al_Buffers);
//     alGenSources(1, &s->al_Source);
//     s->al_toggle = 0;
//     s->al_format = 0;
//     switch (ch)
//     {
//         case 1:
//             s->al_format = AL_FORMAT_MONO16;
//             break;
//         case 2:
//             s->al_format = AL_FORMAT_STEREO16;
//             break;
//         case 4:
//             s->al_format = alGetEnumValue("AL_FORMAT_QUAD16");
//             break;
//         case 6:
//             s->al_format = alGetEnumValue("AL_FORMAT_51CHN16");
//             break;
//     }
//     if (s->al_format == 0)
//         goto error;
//
//     Logger::Write(L"ALSOUND: %08X,CH=%d,FREQ=%d '%s' buffer %d (%d)\n",
//               s->al_format, ch, freq, sound_devices[index].alname,
//               sd->sndbufsize, s->al_bufsize);
//     return 1;
//
// error:
//     close_audio_al(sd);
//     return 0;
// }

static void close_audio_wasapi(struct sound_data* sd)
{
    struct sound_dp* s = sd->data;

    if (s->pRenderClient)
        s->pRenderClient->Release();
    if (s->pAudioVolume)
        s->pAudioVolume->Release();
    #if 0
    if (s->pAudioClock)
        s->pAudioClock->Release();
    #endif
    if (s->pAudioClient)
        s->pAudioClient->Release();
    if (s->pDevice)
        s->pDevice->Release();
    if (s->pEnumerator)
        s->pEnumerator->Release();
}

static int open_audio_wasapi(struct sound_data* sd)
{
    HRESULT hr;
    struct sound_dp* s = sd->data;
    WAVEFORMATEX* pwfx = nullptr, * pwfx_saved = nullptr;
    WAVEFORMATEXTENSIBLE wavfmt;
    int final;
    LPWSTR name = nullptr;
    int rn[4], rncnt;
    // AUDCLNT_SHAREMODE sharemode;
    int size, v;

    // sd->devicetype = SOUND_DEVICE_WASAPI;
    // s->wasapiexclusive = exclusive;

    // if (s->wasapiexclusive)
    //     sharemode = AUDCLNT_SHAREMODE_EXCLUSIVE;
    // else
    //     sharemode = AUDCLNT_SHAREMODE_SHARED;

    hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr,
        CLSCTX_ALL, __uuidof(IMMDeviceEnumerator),
        (void**)&s->pEnumerator);
    if (FAILED(hr))
    {
        Logger::Write(L"WASAPI: %d\n", hr);
        goto error;
    }

    // if (sound_devices[index].alname == nullptr)
    hr = s->pEnumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &s->pDevice);
    // else
    //     hr = s->pEnumerator->GetDevice(sound_devices[index].alname, &s->pDevice);

    if (FAILED(hr))
    {
        Logger::Write(L"WASAPI: GetDefaultAudioEndpoint() %08X\n", hr);
        goto error;
    }

    hr = s->pDevice->GetId(&name);
    if (FAILED(hr))
    {
        Logger::Write(L"WASAPI: GetId() %08X\n", hr);
        goto error;
    }

    hr = s->pDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, (void**)&s->pAudioClient);
    if (FAILED(hr))
    {
        Logger::Write(L"WASAPI: Activate() %d\n", hr);
        goto error;
    }

    hr = s->pAudioClient->GetMixFormat(&pwfx);
    if (FAILED(hr))
    {
        Logger::Write(L"WASAPI: GetMixFormat() %08X\n", hr);
        goto error;
    }

    hr = s->pAudioClient->GetDevicePeriod(nullptr, &s->hnsRequestedDuration);
    if (FAILED(hr))
    {
        Logger::Write(L"WASAPI: GetDevicePeriod() %08X\n", hr);
        goto error;
    }

    final = 0;
    rncnt = 0;
    pwfx_saved = nullptr;
    for (;;)
    {
        // if (sd->channels == 6)
        // {
        //     rn[0] = KSAUDIO_SPEAKER_5POINT1;
        //     rn[1] = KSAUDIO_SPEAKER_5POINT1_SURROUND;
        //     rn[2] = 0;
        // }
        // else if (sd->channels == 4)
        // {
        //     rn[0] = KSAUDIO_SPEAKER_QUAD;
        //     rn[1] = KSAUDIO_SPEAKER_QUAD_SURROUND;
        //     rn[2] = KSAUDIO_SPEAKER_SURROUND;
        //     rn[3] = 0;
        // }
        // else
        if (sd->channels == 2)
        {
            rn[0] = KSAUDIO_SPEAKER_STEREO;
            rn[1] = 0;
        }
        else
        {
            ASSERT(sd->channels == 1);
            rn[0] = KSAUDIO_SPEAKER_MONO;
            rn[1] = 0;
        }

        memset(&wavfmt, 0, sizeof wavfmt);
        wavfmt.Format.nChannels = sd->channels;
        wavfmt.Format.nSamplesPerSec = sd->freq;
        wavfmt.Format.wBitsPerSample = 16;
        wavfmt.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
        wavfmt.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
        wavfmt.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
        wavfmt.Samples.wValidBitsPerSample = 16;
        wavfmt.dwChannelMask = rn[rncnt];
        wavfmt.Format.nBlockAlign = wavfmt.Format.wBitsPerSample / 8 * wavfmt.Format.nChannels;
        wavfmt.Format.nAvgBytesPerSec = wavfmt.Format.nBlockAlign * wavfmt.Format.nSamplesPerSec;
        CoTaskMemFree(pwfx);
        pwfx = nullptr;
        hr = s->pAudioClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &wavfmt.Format, &pwfx);
        if (SUCCEEDED(hr) && hr != S_FALSE)
            break;
        Logger::Write(L"WASAPI: IsFormatSupported(%d,%08X,%d) (%d,%d) %08X\n",
            sd->channels, rn[rncnt], sd->freq,
            pwfx ? pwfx->nChannels : -1, pwfx ? pwfx->nSamplesPerSec : -1,
            hr);
        if (final && SUCCEEDED(hr))
        {
            if (pwfx_saved)
            {
                sd->channels = pwfx_saved->nChannels;
                sd->freq = pwfx_saved->nSamplesPerSec;
                CoTaskMemFree(pwfx);
                pwfx = pwfx_saved;
                pwfx_saved = nullptr;
            }
            break;
        }
        if (hr != AUDCLNT_E_UNSUPPORTED_FORMAT && hr != S_FALSE)
            goto error;
        if (hr == S_FALSE && pwfx_saved == nullptr)
        {
            pwfx_saved = pwfx;
            pwfx = nullptr;
        }
        rncnt++;
        if (rn[rncnt])
            continue;
        if (final)
            goto error;
        rncnt = 0;
        if (sd->freq < 44100)
        {
            sd->freq = 44100;
            continue;
        }
        if (sd->freq < 48000)
        {
            sd->freq = 48000;
            continue;
        }
        if (sd->channels != 2)
        {
            sd->channels = 2;
            continue;
        }
        final = 1;
        if (pwfx_saved == nullptr)
            goto error;
        sd->channels = pwfx_saved->nChannels;
        sd->freq = pwfx_saved->nSamplesPerSec;
    }

    size = sd->sndbufsize * sd->channels * 16 / 8;
    // s->snd_configsize = size;
    sd->sndbufsize = size / 32;
    size /= (sd->channels * 16 / 8);

    s->bufferFrameCount = (UINT32)( // frames =
        1.0 * s->hnsRequestedDuration * // hns *
        wavfmt.Format.nSamplesPerSec / // (frames / s) /
        1000 / // (ms / s) /
        10000 // (hns / s) /
        + 0.5); // rounding

    if (s->bufferFrameCount < size)
    {
        s->bufferFrameCount = size;
        s->hnsRequestedDuration = // hns =
            (REFERENCE_TIME)(
            10000.0 * // (hns / ms) *
            1000 * // (ms / s) *
            s->bufferFrameCount / // frames /
            wavfmt.Format.nSamplesPerSec  // (frames / s)
            + 0.5 // rounding
            );
    }

    hr = s->pAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_NOPERSIST,
        s->hnsRequestedDuration, 0, pwfx ? pwfx : &wavfmt.Format, nullptr);
    if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED)
    {
        hr = s->pAudioClient->GetBufferSize(&s->bufferFrameCount);
        if (FAILED(hr))
        {
            Logger::Write(L"WASAPI: GetBufferSize() %08X\n", hr);
            goto error;
        }
        s->pAudioClient->Release();
        s->hnsRequestedDuration = // hns =
            (REFERENCE_TIME)(
            10000.0 * // (hns / ms) *
            1000 * // (ms / s) *
            s->bufferFrameCount / // frames /
            wavfmt.Format.nSamplesPerSec  // (frames / s)
            + 0.5 // rounding
            );
        s->hnsRequestedDuration *= sd->sndbufsize / 512;
        hr = s->pDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, (void**)&s->pAudioClient);
        if (FAILED(hr))
        {
            Logger::Write(L"WASAPI: Activate() %08X\n", hr);
            goto error;
        }
        hr = s->pAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_NOPERSIST,
            s->hnsRequestedDuration, 0, pwfx ? pwfx : &wavfmt.Format, nullptr);
    }
    if (FAILED(hr))
    {
        Logger::Write(L"WASAPI: Initialize() %08X\n", hr);
        goto error;
    }

    hr = s->pAudioClient->GetBufferSize(&s->bufferFrameCount);
    if (FAILED(hr))
    {
        Logger::Write(L"WASAPI: GetBufferSize() %08X\n", hr);
        goto error;
    }

    s->hnsRequestedDuration = // hns =
        (REFERENCE_TIME)(
        10000.0 * // (hns / ms) *
        1000 * // (ms / s) *
        s->bufferFrameCount / // frames /
        wavfmt.Format.nSamplesPerSec  // (frames / s)
        + 0.5 // rounding
        );

    hr = s->pAudioClient->GetService(__uuidof(IAudioRenderClient), (void**)&s->pRenderClient);
    if (FAILED(hr))
    {
        Logger::Write(L"WASAPI: GetService(IAudioRenderClient) %08X\n", hr);
        goto error;
    }
    hr = s->pAudioClient->GetService(__uuidof(ISimpleAudioVolume), (void**)&s->pAudioVolume);
    if (FAILED(hr))
    {
        Logger::Write(L"WASAPI: GetService(ISimpleAudioVolume) %08X\n", hr);
        goto error;
    }
    #if 0
    hr = s->pAudioClient->GetService(IAudioClock, (void**)&s->pAudioClock);
    if (FAILED(hr))
    {
        Logger::Write(L"WASAPI: GetService(IAudioClock) %08X\n", hr);
    }
    else
    {
        hr = s->pAudioClock->GetFrequency(&s->wasapiclock);
        if (FAILED(hr))
        {
            Logger::Write(L"WASAPI: GetFrequency() %08X\n", hr);
        }
    }
    #endif
    v = s->bufferFrameCount * sd->channels * 16 / 8;
    v /= 2;
    if (sd->sndbufsize > v)
        sd->sndbufsize = v;
    s->wasapigoodsize = s->bufferFrameCount / 2;
    s->sndbufframes = sd->sndbufsize / (sd->channels * 16 / 8);

    if (Logging::SOUND_DEBUG)
    {
        Logger::Write(L"WASAPI: '%s'\nWASAPI: CH=%d FREQ=%d BUF=%d (%d)\n",
            name, sd->channels, sd->freq, sd->sndbufsize, s->bufferFrameCount);
    }

    CoTaskMemFree(pwfx);
    CoTaskMemFree(pwfx_saved);
    CoTaskMemFree(name);

    return 1;

error:
    CoTaskMemFree(pwfx);
    CoTaskMemFree(pwfx_saved);
    CoTaskMemFree(name);
    close_audio_wasapi(sd);
    return 0;
}

// static int open_audio_ds(struct sound_data* sd, int index)
// {
//     struct sound_dp* s = sd->data;
//     HRESULT hr;
//     DSBUFFERDESC sound_buffer;
//     DSCAPS DSCaps;
//     WAVEFORMATEXTENSIBLE wavfmt;
//     LPDIRECTSOUNDBUFFER pdsb;
//     int freq = sd->freq;
//     int ch = sd->channels;
//     int round, i;
//     DWORD speakerconfig;
//     int size;
//
//     sd->devicetype = SOUND_DEVICE_DS;
//     size = sd->sndbufsize * ch * 2;
//     s->snd_configsize = size;
//     sd->sndbufsize = size / 32;
//     if (sd->sndbufsize > SND_MAX_BUFFER)
//         sd->sndbufsize = SND_MAX_BUFFER;
//
//     s->max_sndbufsize = size * 4;
//     if (s->max_sndbufsize > SND_MAX_BUFFER2)
//         s->max_sndbufsize = SND_MAX_BUFFER2;
//     s->dsoundbuf = s->max_sndbufsize * 2;
//
//     if (s->dsoundbuf < DSBSIZE_MIN)
//         s->dsoundbuf = DSBSIZE_MIN;
//     if (s->dsoundbuf > DSBSIZE_MAX)
//         s->dsoundbuf = DSBSIZE_MAX;
//
//     if (s->max_sndbufsize * 2 > s->dsoundbuf)
//         s->max_sndbufsize = s->dsoundbuf / 2;
//     sd->samplesize = sd->channels * 2;
//
//     recalc_offsets(sd);
//
//     hr = DirectSoundCreate8(&sound_devices[index].guid, &s->lpDS, nullptr);
//     if (FAILED(hr))
//     {
//         Logger::Write(L"DSSOUND: DirectSoundCreate8() failure: %s\n", DXError(hr));
//         return 0;
//     }
//
//     hr = IDirectSound_SetCooperativeLevel(s->lpDS, hMainWnd, DSSCL_PRIORITY);
//     if (FAILED(hr))
//     {
//         Logger::Write(L"DSSOUND: Can't set cooperativelevel: %s\n", DXError(hr));
//         goto error;
//     }
//
//     memset(&DSCaps, 0, sizeof(DSCaps));
//     DSCaps.dwSize = sizeof(DSCaps);
//     hr = IDirectSound_GetCaps(s->lpDS, &DSCaps);
//     if (FAILED(hr))
//     {
//         Logger::Write(L"DSSOUND: Error getting DirectSound capabilities: %s\n", DXError(hr));
//         goto error;
//     }
//     if (DSCaps.dwFlags & DSCAPS_EMULDRIVER)
//     {
//         Logger::Write(L"DSSOUND: Emulated DirectSound driver detected, don't complain if sound quality is crap :)\n");
//     }
//     if (DSCaps.dwFlags & DSCAPS_CONTINUOUSRATE)
//     {
//         int minfreq = DSCaps.dwMinSecondarySampleRate;
//         int maxfreq = DSCaps.dwMaxSecondarySampleRate;
//         if (minfreq > freq && freq < 22050)
//         {
//             freq = minfreq;
//             sd->freq = freq;
//             Logger::Write(L"DSSOUND: minimum supported frequency: %d\n", minfreq);
//         }
//         if (maxfreq < freq && freq > 44100)
//         {
//             freq = maxfreq;
//             sd->freq = freq;
//             Logger::Write(L"DSSOUND: maximum supported frequency: %d\n", maxfreq);
//         }
//     }
//
//     speakerconfig = fillsupportedmodes(sd, freq, supportedmodes);
//     Logger::Write(L"DSSOUND: %08X ", speakerconfig);
//     for (i = 0; supportedmodes[i].ch; i++)
//         Logger::Write(L"%d:%08X ", supportedmodes[i].ch, supportedmodes[i].ksmode);
//     Logger::Write(L"\n");
//
//     for (round = 0; supportedmodes[round].ch; round++)
//     {
//         DWORD ksmode = 0;
//
//         pdsb = nullptr;
//         memset(&wavfmt, 0, sizeof(WAVEFORMATEXTENSIBLE));
//         wavfmt.Format.nChannels = ch;
//         wavfmt.Format.nSamplesPerSec = freq;
//         wavfmt.Format.wBitsPerSample = 16;
//         if (supportedmodes[round].ch != ch)
//             continue;
//
//         if (ch <= 2)
//         {
//             wavfmt.Format.wFormatTag = WAVE_FORMAT_PCM;
//         }
//         else
//         {
//             wavfmt.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
//             ksmode = supportedmodes[round].ksmode;
//             wavfmt.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
//             wavfmt.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
//             wavfmt.Samples.wValidBitsPerSample = 16;
//             wavfmt.dwChannelMask = ksmode;
//         }
//         wavfmt.Format.nBlockAlign = wavfmt.Format.wBitsPerSample / 8 * wavfmt.Format.nChannels;
//         wavfmt.Format.nAvgBytesPerSec = wavfmt.Format.nBlockAlign * wavfmt.Format.nSamplesPerSec;
//
//         Logger::Write(L"DSSOUND: %08X,CH=%d,FREQ=%d '%s' buffer %d (%d), dist %d\n",
//                   ksmode, ch, freq, sound_devices[index].name,
//                   s->max_sndbufsize / sd->samplesize, s->max_sndbufsize, s->snd_configsize / sd->samplesize);
//
//         memset(&sound_buffer, 0, sizeof(sound_buffer));
//         sound_buffer.dwSize = sizeof(sound_buffer);
//         sound_buffer.dwBufferBytes = s->dsoundbuf;
//         sound_buffer.lpwfxFormat = &wavfmt.Format;
//         sound_buffer.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS;
//         sound_buffer.dwFlags |= DSBCAPS_CTRLVOLUME | (ch >= 4 ? DSBCAPS_LOCHARDWARE : DSBCAPS_LOCSOFTWARE);
//         sound_buffer.guid3DAlgorithm = GUID_NULL;
//
//         hr = IDirectSound_CreateSoundBuffer(s->lpDS, &sound_buffer, &pdsb, nullptr);
//         if (SUCCEEDED(hr))
//             break;
//         if (sound_buffer.dwFlags & DSBCAPS_LOCHARDWARE)
//         {
//             HRESULT hr2 = hr;
//             sound_buffer.dwFlags &= ~DSBCAPS_LOCHARDWARE;
//             sound_buffer.dwFlags |= DSBCAPS_LOCSOFTWARE;
//             hr = IDirectSound_CreateSoundBuffer(s->lpDS, &sound_buffer, &pdsb, nullptr);
//             if (SUCCEEDED(hr))
//             {
//                 //Logger::Write(L"DSSOUND: Couldn't use hardware buffer (switched to software): %s\n", DXError (hr2));
//                 break;
//             }
//         }
//         Logger::Write(L"DSSOUND: Secondary CreateSoundBuffer() failure: %s\n", DXError(hr));
//     }
//
//     if (pdsb == nullptr)
//         goto error;
//     hr = pdsb->QueryInterface(IID_IDirectSoundBuffer8, (LPVOID*)&s->lpDSBsecondary);
//     IDirectSound_Release(pdsb);
//     if (FAILED(hr))
//     {
//         Logger::Write(L"DSSOUND: Secondary QueryInterface() failure: %s\n", DXError(hr));
//         goto error;
//     }
//     clearbuffer(sd);
//
//     return 1;
//
// error:
//     close_audio_ds(sd);
//     return 0;
// }

int open_sound_device(struct sound_data* sd, int bufsize, int freq, int channels)
{
    int ret = 0;
    struct sound_dp* sdp = xcalloc(struct sound_dp, 1);
    sd->data = sdp;
    sd->sndbufsize = bufsize;
    sd->freq = freq;
    sd->channels = channels;
    sd->paused = 1;
    // if (sound_devices[index].type == SOUND_DEVICE_AL)
    ret = open_audio_wasapi(sd);
    // else if (sound_devices[index].type == SOUND_DEVICE_DS)
    //     ret = open_audio_ds(sd, index);
    // else if (sound_devices[index].type == SOUND_DEVICE_PA)
    //     ret = open_audio_pa(sd, index);
    // else if (sound_devices[index].type == SOUND_DEVICE_WASAPI)
    //     ret = open_audio_wasapi(sd, index, exclusive);

    sd->samplesize = sd->channels * 2;
    return ret;
}

void close_sound_device(struct sound_data* sd)
{
    pause_sound_device(sd);
    // if (sd->devicetype == SOUND_DEVICE_AL)
    //     close_audio_al(sd);
    // else if (sd->devicetype == SOUND_DEVICE_DS)
    //     close_audio_ds(sd);
    // else if (sd->devicetype == SOUND_DEVICE_PA)
    //     close_audio_pa(sd);
    // else if (sd->devicetype == SOUND_DEVICE_WASAPI)
    close_audio_wasapi(sd);
    free(sd->data);
    sd->data = nullptr;
}

void pause_sound_device(struct sound_data* sd)
{
    sd->paused = 1;
    // if (sd->devicetype == SOUND_DEVICE_AL)
    //     pause_audio_al(sd);
    // else if (sd->devicetype == SOUND_DEVICE_DS)
    //     pause_audio_ds(sd);
    // else if (sd->devicetype == SOUND_DEVICE_PA)
    //     pause_audio_pa(sd);
    // else if (sd->devicetype == SOUND_DEVICE_WASAPI)
    pause_audio_wasapi(sd);
}

void resume_sound_device(struct sound_data* sd)
{
    // if (sd->devicetype == SOUND_DEVICE_AL)
    //     resume_audio_al(sd);
    // else if (sd->devicetype == SOUND_DEVICE_DS)
    //     resume_audio_ds(sd);
    // else if (sd->devicetype == SOUND_DEVICE_PA)
    //     resume_audio_pa(sd);
    // else if (sd->devicetype == SOUND_DEVICE_WASAPI)
    resume_audio_wasapi(sd);
    sd->paused = 0;
}

static int open_sound()
{
    int ret = 0, /*num,*/ ch;
    int size = g_curr_conf.sound_maxbsiz;

    // if (!g_curr_conf.sound_produce)
    //     return 0;

    config_changed = 1;
    /* Always interpret buffer size as number of samples, not as actual
       buffer size.  Of course, since 8192 is the default, we'll have to
       scale that to a sane value (assuming that otherwise 16 bits and
       stereo would have been enabled and we'd have done the shift by
       two anyway).  */
    size >>= 2;
    if (size & (size - 1))
        size = DEFAULT_SOUND_MAXB;
    if (size < 512)
        size = 512;

    // num = enumerate_sound_devices();
    // if (g_curr_conf.win32_soundcard >= num)
    //     g_curr_conf.win32_soundcard = g_changed_conf.win32_soundcard = 0;
    ch = 2;
    ret = open_sound_device(sdp, size, Sound::SOUND_FREQ, ch);
    if (!ret)
        return 0;

    ASSERT(Sound::SOUND_FREQ == sdp->freq);

    set_volume(g_curr_conf.sound_volume, sdp->mute);
    // if (get_audio_amigachannels(g_curr_conf.sound_stereo) == 4)
    //     sample_handler = sample16ss_handler;
    // else
    sample_handler = /*get_audio_ismono(g_curr_conf.sound_stereo) ? sample16_handler :*/ sample16s_handler;

    sdp->obtainedfreq = sdp->freq;

    have_sound = 1;
    sound_available = 1;
    update_sound(fake_vblank_hz, 1, g_curr_conf.ntscmode);
    paula_sndbufsize = sdp->sndbufsize;
    paula_sndbufpt = paula_sndbuffer;
    Floppy::Sound::Init();

    return 1;
}

void close_sound()
{
    for (int i = 0; i < MAX_SOUND_DEVICES; i++)
    {
        free(record_devices[i].name);
        record_devices[i].name = nullptr;

        free(record_devices[i].cfgname);
        record_devices[i].cfgname = nullptr;

        free(record_devices[i].alname);
        record_devices[i].alname = nullptr;
    }

    config_changed = 1;
    gui_data.sndbuf = 0;
    gui_data.sndbuf_status = 3;
    if (!have_sound)
        return;
    close_sound_device(sdp);
    have_sound = 0;

    Floppy::Sound::Clear();
}

int init_sound()
{
    gui_data.sndbuf_status = 3;
    gui_data.sndbuf = 0;
    if (!sound_available)
        return 0;
    if (!g_curr_conf.sound_produce)
        return 0;
    if (have_sound)
        return 1;
    if (!open_sound())
        return 0;
    sdp->paused = 1;

    // driveclick_reset();

    resume_sound();
    return 1;
}

void pause_sound()
{
    if (sdp->paused)
        return;
    if (!have_sound)
        return;
    pause_sound_device(sdp);
}

void resume_sound()
{
    if (!sdp->paused)
        return;
    if (!have_sound)
        return;
    resume_sound_device(sdp);
}

// void reset_sound()
// {
//     if (!have_sound)
//         return;
//     clearbuffer(sdp);
// }

static void disable_sound()
{
    close_sound();
    g_curr_conf.sound_produce = g_changed_conf.sound_produce = false;
}

static void reopen_sound()
{
    close_sound();
    open_sound();
}

extern int vsynctime_orig;

#ifndef AVIOUTPUT
static int avioutput_audio;
#endif

void sound_setadjust(double v)
{
    float mult;

    if (v >= -5 && v <= 5)
        v = 0;

    mult = (1000.0 + v);
    if (avioutput_audio && avioutput_enabled && avioutput_nosoundsync)
        mult = 1000.0;
    if (isvsync() || (avioutput_audio && avioutput_enabled && !g_curr_conf.jit_cache_size))
    {
        vsynctime = vsynctime_orig;
        scaled_sample_evtime = scaled_sample_evtime_orig * mult / 1000.0;
    }
    else if (g_curr_conf.jit_cache_size || g_curr_conf.m68k_speed != 0)
    {
        vsynctime = (long)(((double)vsynctime_orig) * mult / 1000.0);
        scaled_sample_evtime = scaled_sample_evtime_orig;
    }
    else
    {
        vsynctime = (long)(((double)vsynctime_orig) * mult / 1000.0);
        scaled_sample_evtime = scaled_sample_evtime_orig;
    }
}

#define SND_STATUSCNT 10

// #define cf(x) if ((x) >= s->dsoundbuf) (x) -= s->dsoundbuf;
//
// static void restart_sound_buffer2(struct sound_data* sd)
// {
//     struct sound_dp* s = sd->data;
//     DWORD playpos, safed;
//     HRESULT hr;
//
//     if (sd->devicetype != SOUND_DEVICE_DS)
//         return;
//     if (sdp->waiting_for_buffer != -1)
//         return;
//     hr = IDirectSoundBuffer_GetCurrentPosition(s->lpDSBsecondary, &playpos, &safed);
//     if (FAILED(hr))
//     {
//         Logger::Write(L"DSSOUND: DirectSoundBuffer_GetCurrentPosition failed, %s\n", DXError(hr));
//         return;
//     }
//     s->writepos = safed + s->snd_writeoffset;
//     if (s->writepos < 0)
//         s->writepos += s->dsoundbuf;
//     cf(s->writepos);
// }
//
// void restart_sound_buffer()
// {
//     restart_sound_buffer2(sdp);
// }
//
// static int alcheck(struct sound_data* sd, int v)
// {
//     struct sound_dp* s = sd->data;
//     int err = alGetError();
//     if (err != AL_NO_ERROR)
//     {
//         int v1, v2, v3;
//         alGetSourcei(s->al_Source, AL_BUFFERS_PROCESSED, &v1);
//         alGetSourcei(s->al_Source, AL_BUFFERS_QUEUED, &v2);
//         alGetSourcei(s->al_Source, AL_SOURCE_STATE, &v3);
//         Logger::Write(L"OpenAL %d: error %d. PROC=%d QUEUE=%d STATE=%d\n", v, err, v1, v2, v3);
//         Logger::Write(L"           %d %08x %08x %08x %d %d\n",
//                   s->al_toggle, s->al_Buffers[s->al_toggle], s->al_format, s->al_bigbuffer, s->al_bufsize, sd->freq);
//         return 1;
//     }
//     return 0;
// }
//
// static void finish_sound_buffer_al(struct sound_data* sd, ushort* sndbuffer)
// {
//     struct sound_dp* s = sd->data;
//     static int tfprev;
//     static int statuscnt;
//     int v, v2;
//     double m, skipmode;
//
//     if (!sd->waiting_for_buffer)
//         return;
//     if (savestate_state)
//         return;
//
//     if (sd == sdp)
//     {
//         if (statuscnt > 0)
//         {
//             statuscnt--;
//             if (statuscnt == 0)
//                 gui_data.sndbuf_status = 0;
//         }
//         if (gui_data.sndbuf_status == 3)
//             gui_data.sndbuf_status = 0;
//     }
//     alGetError();
//
//     memcpy(s->al_bigbuffer + s->al_offset, sndbuffer, sd->sndbufsize);
//     s->al_offset += sd->sndbufsize;
//     if (s->al_offset >= s->al_bufsize)
//     {
//         ALuint tmp;
//         alGetSourcei(s->al_Source, AL_BUFFERS_PROCESSED, &v);
//         while (v == 0 && sd->waiting_for_buffer < 0)
//         {
//             sleep_millis(1);
//             alGetSourcei(s->al_Source, AL_SOURCE_STATE, &v);
//             if (v != AL_PLAYING)
//                 break;
//             alGetSourcei(s->al_Source, AL_BUFFERS_PROCESSED, &v);
//         }
//
//         alSourceUnqueueBuffers(s->al_Source, 1, &tmp);
//         alGetError();
//
//         //	Logger::Write(L"           %d %08x %08x %08x %d %d\n",
//         //	    al_toggle, al_Buffers[al_toggle], al_format, al_bigbuffer, al_bufsize, g_curr_conf.sound_freq);
//
//         alBufferData(s->al_Buffers[s->al_toggle], s->al_format, s->al_bigbuffer, s->al_bufsize, sd->freq);
//         alcheck(sd, 4);
//         alSourceQueueBuffers(s->al_Source, 1, &s->al_Buffers[s->al_toggle]);
//         alcheck(sd, 2);
//         s->al_toggle++;
//         if (s->al_toggle >= AL_BUFFERS)
//             s->al_toggle = 0;
//
//         alGetSourcei(s->al_Source, AL_BUFFERS_QUEUED, &v2);
//         alcheck(sd, 5);
//         alGetSourcei(s->al_Source, AL_SOURCE_STATE, &v);
//         alcheck(sd, 3);
//         if (v != AL_PLAYING && v2 >= AL_BUFFERS)
//         {
//             if (sd->waiting_for_buffer > 0)
//             {
//                 Logger::Write(L"AL SOUND PLAY!\n");
//                 alSourcePlay(s->al_Source);
//                 sd->waiting_for_buffer = -1;
//                 tfprev = timeframes + 10;
//                 tfprev = (tfprev / 10) * 10;
//             }
//             else
//             {
//                 gui_data.sndbuf_status = 2;
//                 statuscnt = SND_STATUSCNT;
//                 Logger::Write(L"AL underflow\n");
//                 clearbuffer(sd);
//                 sd->waiting_for_buffer = 1;
//             }
//         }
//         s->al_offset = 0;
//     }
//     alcheck(sd, 1);
//
//     alGetSourcei(s->al_Source, AL_SOURCE_STATE, &v);
//     alcheck(sd, 6);
//
//     if (v == AL_PLAYING && sd == sdp)
//     {
//         alGetSourcei(s->al_Source, AL_BYTE_OFFSET, &v);
//         alcheck(sd, 7);
//         v -= s->al_offset;
//         gui_data.sndbuf = 100 * v / sd->sndbufsize;
//         m = gui_data.sndbuf / 100.0;
//
//         if (isvsync())
//         {
//
//             skipmode = pow(m < 0 ? -m : m, EXP) / 8;
//             if (m < 0)
//                 skipmode = -skipmode;
//             if (skipmode < -ADJUST_VSSIZE)
//                 skipmode = -ADJUST_VSSIZE;
//             if (skipmode > ADJUST_VSSIZE)
//                 skipmode = ADJUST_VSSIZE;
//
//         }
//         else
//         {
//
//             skipmode = pow(m < 0 ? -m : m, EXP) / 2;
//             if (m < 0)
//                 skipmode = -skipmode;
//             if (skipmode < -ADJUST_SIZE)
//                 skipmode = -ADJUST_SIZE;
//             if (skipmode > ADJUST_SIZE)
//                 skipmode = ADJUST_SIZE;
//
//         }
//         if (tfprev != timeframes)
//         {
//             if ((0 || sound_debug) && !(tfprev % 10))
//                 Logger::Write(L"s=%+02.1f\n", skipmode);
//             tfprev = timeframes;
//             sound_setadjust(skipmode);
//         }
//     }
//
//     alcheck(sd, 0);
// }
//
// int blocking_sound_device(struct sound_data* sd)
// {
//     struct sound_dp* s = sd->data;
//
//     if (sd->devicetype == SOUND_DEVICE_DS)
//     {
//
//         HRESULT hr;
//         DWORD playpos, safepos;
//         int diff;
//
//         hr = IDirectSoundBuffer_GetCurrentPosition(s->lpDSBsecondary, &playpos, &safepos);
//         if (FAILED(hr))
//         {
//             restore_ds(sd, hr);
//             Logger::Write(L"DSSOUND: GetCurrentPosition failed: %s\n", DXError(hr));
//             return -1;
//         }
//         if (s->writepos >= safepos)
//             diff = s->writepos - safepos;
//         else
//             diff = s->dsoundbuf - safepos + s->writepos;
//         if (diff > s->snd_maxoffset)
//             return 1;
//         return 0;
//
//     }
//     else if (sd->devicetype == SOUND_DEVICE_AL)
//     {
//
//         int v = 0;
//         alGetError();
//         alGetSourcei(s->al_Source, AL_BUFFERS_QUEUED, &v);
//         if (alGetError() != AL_NO_ERROR)
//             return -1;
//         if (v < AL_BUFFERS)
//             return 0;
//         return 1;
//
//     }
//     else if (sd->devicetype == SOUND_DEVICE_WASAPI)
//     {
//
//         //	if (WaitForSingleObject (s->wasapihandle, 0) == WAIT_TIMEOUT)
//         //	    return 0;
//         return 1;
//
//     }
//     return -1;
// }
//
// int get_offset_sound_device(struct sound_data* sd)
// {
//     struct sound_dp* s = sd->data;
//
//     if (sd->devicetype == SOUND_DEVICE_DS)
//     {
//         HRESULT hr;
//         DWORD playpos, safedist, status;
//
//         hr = IDirectSoundBuffer_GetStatus(s->lpDSBsecondary, &status);
//         hr = IDirectSoundBuffer_GetCurrentPosition(s->lpDSBsecondary, &playpos, &safedist);
//         if (FAILED(hr))
//             return -1;
//         playpos -= s->writepos;
//         if (playpos < 0)
//             playpos += s->dsoundbuf;
//         return playpos;
//     }
//     else if (sd->devicetype == SOUND_DEVICE_AL)
//     {
//         int v;
//         alGetError();
//         alGetSourcei(s->al_Source, AL_BYTE_OFFSET, &v);
//         if (alGetError() == AL_NO_ERROR)
//             return v;
//     }
//     return -1;
// }

static double sync_sound(double m)
{
    double skipmode;
    if (isvsync())
    {
        skipmode = pow(m < 0 ? -m : m, EXP) / 10;
        if (m < 0)
            skipmode = -skipmode;
        if (skipmode < -ADJUST_VSSIZE)
            skipmode = -ADJUST_VSSIZE;
        if (skipmode > ADJUST_VSSIZE)
            skipmode = ADJUST_VSSIZE;
    }
    else if (1)
    {
        skipmode = pow(m < 0 ? -m : m, EXP) / 2;
        if (m < 0)
            skipmode = -skipmode;
        if (skipmode < -ADJUST_SIZE)
            skipmode = -ADJUST_SIZE;
        if (skipmode > ADJUST_SIZE)
            skipmode = ADJUST_SIZE;
    }

    return skipmode;
}

static void finish_sound_buffer_wasapi(struct sound_data* sd, ushort* sndbuffer)
{
    struct sound_dp* s = sd->data;
    HRESULT hr;
    BYTE* pData;
    double skipmode;
    UINT32 numFramesPadding;
    int avail;
    int stuck = 2000;
    int oldpadding = 0;

    if (sd->paused)
        return;

    s->framecounter++;
    if (s->framecounter > 50)
    {
        s->sndbuf = s->sndbuf / s->framecounter;
        s->framecounter = 2;
    }

    for (;;)
    {
        hr = s->pAudioClient->GetCurrentPadding(&numFramesPadding);
        if (FAILED(hr))
        {
            Logger::Write(L"WASAPI: GetCurrentPadding() %08X\n", hr);
            return;
        }
        avail = s->bufferFrameCount - numFramesPadding;
        if (avail >= s->sndbufframes)
            break;
        gui_data.sndbuf_status = 1;
        statuscnt = SND_STATUSCNT;
        sleep_millis(1);
        if (oldpadding == numFramesPadding)
        {
            if (stuck-- < 0)
            {
                Logger::Write(L"WASAPI: sound stuck %d %d %d !?\n", s->bufferFrameCount, numFramesPadding, s->sndbufframes);
                reopen_sound();
                return;
            }
        }
        oldpadding = numFramesPadding;
    }
    s->sndbuf += (s->wasapigoodsize - avail) * 1000 / s->wasapigoodsize;
    gui_data.sndbuf = s->sndbuf / s->framecounter;
    if (s->framecounter == 2)
    {
        skipmode = sync_sound(gui_data.sndbuf / 70.0);
        sound_setadjust(skipmode);
    }
    hr = s->pRenderClient->GetBuffer(s->sndbufframes, &pData);
    if (SUCCEEDED(hr))
    {
        memcpy(pData, sndbuffer, sd->sndbufsize);
        s->pRenderClient->ReleaseBuffer(s->sndbufframes, 0);
    }

    if (record_sound)
    {
        static FILE* file = nullptr;

        if (file == nullptr)
            file = fopen("audio.data", "w");

        for (int i = 0; i < sd->sndbufsize; i += 4)
        {
            int v1 = (int)(short)sndbuffer[i / 2];
            int v2 = (int)(short)sndbuffer[(i + 2) / 2];
            fprintf(file, "%d, %d\n", v1, v2);
        }

        fflush(file);
    }
}

// static void finish_sound_buffer_ds(struct sound_data* sd, ushort* sndbuffer)
// {
//     struct sound_dp* s = sd->data;
//     static int tfprev;
//     DWORD playpos, safepos, status;
//     HRESULT hr;
//     void* b1, * b2;
//     DWORD s1, s2;
//     int diff;
//     int counter;
//
//     if (!sd->waiting_for_buffer)
//         return;
//
//     if (sd->waiting_for_buffer == 1)
//     {
//         hr = s->lpDSBsecondary->Play(0, 0, DSBPLAY_LOOPING);
//         if (FAILED(hr))
//         {
//             Logger::Write(L"DSSOUND: Play failed: %s\n", DXError(hr));
//             restore_ds(sd, DSERR_BUFFERLOST);
//             sd->waiting_for_buffer = 0;
//             return;
//         }
//         hr = s->lpDSBsecondary->SetCurrentPosition(0);
//         if (FAILED(hr))
//         {
//             Logger::Write(L"DSSOUND: 1st SetCurrentPosition failed: %s\n", DXError(hr));
//             restore_ds(sd, DSERR_BUFFERLOST);
//             sd->waiting_for_buffer = 0;
//             return;
//         }
//         /* there are crappy drivers that return PLAYCURSOR = WRITECURSOR = 0 without this.. */
//         counter = 5000;
//         for (;;)
//         {
//             hr = s->lpDSBsecondary->GetCurrentPosition(&playpos, &s->safedist);
//             if (playpos > 0)
//                 break;
//             sleep_millis(1);
//             counter--;
//             if (counter < 0)
//             {
//                 Logger::Write(L"DSSOUND: stuck?!?!\n");
//                 disable_sound();
//                 break;
//             }
//         }
//         Logger::Write(L"DSSOUND: %d = (%d - %d)\n", (s->safedist - playpos) / sd->samplesize, s->safedist / sd->samplesize, playpos / sd->samplesize);
//         recalc_offsets(sd);
//         s->safedist -= playpos;
//         if (s->safedist < 64)
//             s->safedist = 64;
//         cf(s->safedist);
//         #if 0
//         snd_totalmaxoffset_uf += s->safedist;
//         cf(snd_totalmaxoffset_uf);
//         snd_totalmaxoffset_of += s->safedist;
//         cf(snd_totalmaxoffset_of);
//         snd_maxoffset += s->safedist;
//         cf(snd_maxoffset);
//         snd_writeoffset += s->safedist;
//         cf(snd_writeoffset);
//         #endif
//         sd->waiting_for_buffer = -1;
//         restart_sound_buffer2(sd);
//         Logger::Write(L"DSSOUND: bs=%d w=%d max=%d tof=%d tuf=%d\n",
//                   sd->sndbufsize / sd->samplesize, s->snd_writeoffset / sd->samplesize,
//                   s->snd_maxoffset / sd->samplesize, s->snd_totalmaxoffset_of / sd->samplesize,
//                   s->snd_totalmaxoffset_uf / sd->samplesize);
//         tfprev = timeframes + 10;
//         tfprev = (tfprev / 10) * 10;
//     }
//
//     counter = 5000;
//     hr = s->lpDSBsecondary->GetStatus(&status);
//     if (FAILED(hr))
//     {
//         Logger::Write(L"DSSOUND: GetStatus() failed: %s\n", DXError(hr));
//         restore_ds(sd, DSERR_BUFFERLOST);
//         return;
//     }
//     if (status & DSBSTATUS_BUFFERLOST)
//     {
//         Logger::Write(L"DSSOUND: buffer lost\n");
//         restore_ds(sd, DSERR_BUFFERLOST);
//         return;
//     }
//     if ((status & (DSBSTATUS_PLAYING | DSBSTATUS_LOOPING)) != (DSBSTATUS_PLAYING | DSBSTATUS_LOOPING))
//     {
//         Logger::Write(L"DSSOUND: status = %08X\n", status);
//         restore_ds(sd, DSERR_BUFFERLOST);
//         return;
//     }
//     for (;;)
//     {
//         hr = s->lpDSBsecondary->GetCurrentPosition(&playpos, &safepos);
//         if (FAILED(hr))
//         {
//             restore_ds(sd, hr);
//             Logger::Write(L"DSSOUND: GetCurrentPosition failed: %s\n", DXError(hr));
//             return;
//         }
//         if (s->writepos >= safepos)
//             diff = s->writepos - safepos;
//         else
//             diff = s->dsoundbuf - safepos + s->writepos;
//
//         if (diff < sd->sndbufsize || diff > s->snd_totalmaxoffset_uf)
//         {
//             #if 0
//             hr = IDirectSoundBuffer_Lock(lpDSBsecondary, s->writepos, sndbufsize, &b1, &s1, &b2, &s2, 0);
//             if (SUCCEEDED(hr))
//             {
//                 memset(b1, 0, s1);
//                 if (b2)
//                     memset(b2, 0, s2);
//                 IDirectSoundBuffer_Unlock(lpDSBsecondary, b1, s1, b2, s2);
//             }
//             #endif
//             gui_data.sndbuf_status = -1;
//             statuscnt = SND_STATUSCNT;
//             if (diff > s->snd_totalmaxoffset_uf)
//                 s->writepos += s->dsoundbuf - diff;
//             s->writepos += sd->sndbufsize;
//             cf(s->writepos);
//             diff = s->safedist;
//             break;
//         }
//
//         if (diff > s->snd_totalmaxoffset_of)
//         {
//             gui_data.sndbuf_status = 2;
//             statuscnt = SND_STATUSCNT;
//             restart_sound_buffer2(sd);
//             diff = s->snd_writeoffset;
//             Logger::Write(L"DSSOUND: underflow (%d %d)\n", diff / sd->samplesize, s->snd_totalmaxoffset_of / sd->samplesize);
//             break;
//         }
//
//         if (diff > s->snd_maxoffset)
//         {
//             gui_data.sndbuf_status = 1;
//             statuscnt = SND_STATUSCNT;
//             sleep_millis(1);
//             counter--;
//             if (counter < 0)
//             {
//                 Logger::Write(L"DSSOUND: sound system got stuck!?\n");
//                 restore_ds(sd, DSERR_BUFFERLOST);
//                 reopen_sound();
//                 return;
//             }
//             continue;
//         }
//         break;
//     }
//
//     hr = s->lpDSBsecondary->Lock(s->writepos, sd->sndbufsize, &b1, &s1, &b2, &s2, 0);
//     if (restore_ds(sd, hr))
//         return;
//     if (FAILED(hr))
//     {
//         Logger::Write(L"DSSOUND: lock failed: %s (%d %d)\n", DXError(hr), s->writepos / sd->samplesize, sd->sndbufsize / sd->samplesize);
//         return;
//     }
//     memcpy(b1, sndbuffer, s1);
//     if (b2)
//         memcpy(b2, (byte*)sndbuffer + s1, s2);
//     s->lpDSBsecondary->Unlock(b1, s1, b2, s2);
//
//     if (sd == sdp)
//     {
//         double vdiff, m, skipmode;
//
//         vdiff = diff - s->snd_writeoffset;
//         m = 100.0 * vdiff / s->max_sndbufsize;
//         skipmode = sync_sound(m);
//
//         if (tfprev != timeframes)
//         {
//             gui_data.sndbuf = vdiff * 1000 / (s->snd_maxoffset - s->snd_writeoffset);
//             if ((0 || sound_debug) && !(tfprev % 10))
//                 Logger::Write(L"b=%4d,%5d,%5d,%5d d=%5d vd=%5.0f s=%+02.1f\n",
//                           sd->sndbufsize / sd->samplesize, s->snd_configsize / sd->samplesize, s->max_sndbufsize / sd->samplesize,
//                           s->dsoundbuf / sd->samplesize, diff / sd->samplesize, vdiff, skipmode);
//             tfprev = timeframes;
//             sound_setadjust(skipmode);
//         }
//     }
//
//     s->writepos += sd->sndbufsize;
//     cf(s->writepos);
// }
//
// static void channelswap(short* sndbuffer, int len)
// {
//     int i;
//     for (i = 0; i < len; i += 2)
//     {
//         short t;
//         t = sndbuffer[i];
//         sndbuffer[i] = sndbuffer[i + 1];
//         sndbuffer[i + 1] = t;
//     }
// }
// static void channelswap6(short* sndbuffer, int len)
// {
//     int i;
//     for (i = 0; i < len; i += 6)
//     {
//         short t;
//         t = sndbuffer[i + 0];
//         sndbuffer[i + 0] = sndbuffer[i + 1];
//         sndbuffer[i + 1] = t;
//         t = sndbuffer[i + 4];
//         sndbuffer[i + 4] = sndbuffer[i + 5];
//         sndbuffer[i + 5] = t;
//     }
// }

void send_sound(struct sound_data* sd, ushort* sndbuffer)
{
    if (savestate_state)
        return;
    // if (sd->devicetype == SOUND_DEVICE_AL)
    //     finish_sound_buffer_al(sd, sndbuffer);
    // else if (sd->devicetype == SOUND_DEVICE_DS)
    //     finish_sound_buffer_ds(sd, sndbuffer);
    // else if (sd->devicetype == SOUND_DEVICE_PA)
    //     finish_sound_buffer_pa(sd, sndbuffer);
    // else if (sd->devicetype == SOUND_DEVICE_WASAPI)
    finish_sound_buffer_wasapi(sd, sndbuffer);
}

void finish_sound_buffer()
{
    if (g_curr_conf.turbo_emulation)
        return;
    // if (g_curr_conf.sound_stereo_swap_paula)
    // {
    //     if (get_audio_nativechannels(g_curr_conf.sound_stereo) == 2 || get_audio_nativechannels(g_curr_conf.sound_stereo) == 4)
    //         channelswap((short*)paula_sndbuffer, sdp->sndbufsize / 2);
    //     else if (get_audio_nativechannels(g_curr_conf.sound_stereo) == 6)
    //         channelswap6((short*)paula_sndbuffer, sdp->sndbufsize / 2);
    // }
    #ifdef DRIVESOUND
    Floppy::Sound::mix((short*)paula_sndbuffer, sdp->sndbufsize / 2);
    #endif
    #ifdef AVIOUTPUT
    if (avioutput_enabled && avioutput_audio)
        AVIOutput_WriteAudio((byte*)paula_sndbuffer, sdp->sndbufsize);
    if (avioutput_enabled && (!avioutput_framelimiter || avioutput_nosoundoutput))
        return;
    #endif
    if (!have_sound)
        return;
    if (statuscnt > 0)
    {
        statuscnt--;
        if (statuscnt == 0)
            gui_data.sndbuf_status = 0;
    }
    if (gui_data.sndbuf_status == 3)
        gui_data.sndbuf_status = 0;
    send_sound(sdp, paula_sndbuffer);
}

static BOOL CALLBACK DSEnumProc(LPGUID lpGUID, LPCTSTR lpszDesc, LPCTSTR lpszDrvName, LPVOID lpContext)
{
    struct sound_device* sd = (struct sound_device*)lpContext;
    int i;

    for (i = 0; i < MAX_SOUND_DEVICES; i++)
    {
        if (sd[i].name == nullptr)
            break;
    }
    if (i >= MAX_SOUND_DEVICES)
        return TRUE;
    if (lpGUID != nullptr)
        memcpy(&sd[i].guid, lpGUID, sizeof(GUID));
    sd[i].name = _tcsdup(lpszDesc);
    // sd[i].type = SOUND_DEVICE_DS;
    sd[i].cfgname = _tcsdup(sd[i].name);
    return TRUE;
}

// static void wasapi_enum(struct sound_device* sd)
// {
//     HRESULT hr;
//     IMMDeviceEnumerator* enumerator;
//     IMMDeviceCollection* col;
//     int i, cnt;
//
//     Logger::Write(L"Enumerating WASAPI devices...\n");
//     for (cnt = 0; cnt < MAX_SOUND_DEVICES; cnt++)
//     {
//         if (sd->name == nullptr)
//             break;
//         sd++;
//     }
//     if (cnt >= MAX_SOUND_DEVICES)
//         return;
//
//     hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr,
//                           CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&enumerator);
//     if (SUCCEEDED(hr))
//     {
//         hr = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &col);
//         if (SUCCEEDED(hr))
//         {
//             UINT num;
//             hr = col->GetCount(&num);
//             if (SUCCEEDED(hr))
//             {
//                 for (i = 0; i < num && cnt < MAX_SOUND_DEVICES; i++)
//                 {
//                     IMMDevice* dev;
//                     LPWSTR devid = nullptr;
//                     LPWSTR devname = nullptr;
//                     hr = col->Item(i, &dev);
//                     if (SUCCEEDED(hr))
//                     {
//                         IPropertyStore* prop;
//                         dev->GetId(&devid);
//                         hr = dev->OpenPropertyStore(STGM_READ, &prop);
//                         if (SUCCEEDED(hr))
//                         {
//                             PROPVARIANT pv;
//                             PropVariantInit(&pv);
//                             hr = prop->GetValue(PKEY_Device_FriendlyName, &pv);
//                             if (SUCCEEDED(hr))
//                             {
//                                 devname = _tcsdup(pv.pwszVal);
//                             }
//                             PropVariantClear(&pv);
//                             prop->Release();
//                         }
//                         dev->Release();
//                     }
//                     if (devid && devname)
//                     {
//                         TCHAR tmp[MAX_DPATH];
//                         if (i == 0)
//                         {
//                             sd->cfgname = _tcsdup(L"WASAPI:Default Audio Device");
//                             sd->type = SOUND_DEVICE_WASAPI;
//                             sd->name = _tcsdup(L"Default Audio Device");
//                             sd->alname = nullptr;
//                             sd++;
//                             cnt++;
//                         }
//                         if (cnt < MAX_SOUND_DEVICES)
//                         {
//                             _stprintf(tmp, L"WASAPI:%s", devname);
//                             sd->cfgname = _tcsdup(tmp);
//                             sd->type = SOUND_DEVICE_WASAPI;
//                             sd->name = _tcsdup(devname);
//                             sd->alname = _tcsdup(devid);
//                             sd++;
//                             cnt++;
//                         }
//                     }
//                     free(devname);
//                     CoTaskMemFree(devid);
//                 }
//             }
//             col->Release();
//         }
//         enumerator->Release();
//     }
// }
//
// static void OpenALEnumerate(struct sound_device* sds, const char* pDeviceNames, const char* ppDefaultDevice, int skipdetect)
// {
//     struct sound_device* sd;
//     while (pDeviceNames && *pDeviceNames)
//     {
//         ALCdevice* pDevice;
//         const char* devname;
//         int i, ok;
//         for (i = 0; i < MAX_SOUND_DEVICES; i++)
//         {
//             sd = &sds[i];
//             if (sd->name == nullptr)
//                 break;
//         }
//         if (i >= MAX_SOUND_DEVICES)
//             return;
//         devname = pDeviceNames;
//         if (ppDefaultDevice)
//             devname = ppDefaultDevice;
//         ok = 0;
//         if (!skipdetect)
//         {
//             pDevice = alcOpenDevice(devname);
//             if (pDevice)
//             {
//                 ALCcontext* context = alcCreateContext(pDevice, nullptr);
//                 if (context)
//                 {
//                     ALint iMajorVersion = 0, iMinorVersion = 0;
//                     alcMakeContextCurrent(context);
//                     alcGetIntegerv(pDevice, ALC_MAJOR_VERSION, sizeof(ALint), &iMajorVersion);
//                     alcGetIntegerv(pDevice, ALC_MINOR_VERSION, sizeof(ALint), &iMinorVersion);
//                     if (iMajorVersion > 1 || (iMajorVersion == 1 && iMinorVersion > 0))
//                     {
//                         ok = 1;
//                     }
//                     alcMakeContextCurrent(nullptr);
//                     alcDestroyContext(context);
//                 }
//                 alcCloseDevice(pDevice);
//             }
//         }
//         else
//         {
//             ok = 1;
//         }
//         if (ok)
//         {
//             TCHAR tmp[MAX_DPATH];
//             sd->type = SOUND_DEVICE_AL;
//             if (ppDefaultDevice)
//             {
//                 TCHAR* tdevname = Unicode::au(devname);
//                 _stprintf(tmp, L"Default [%s]", tdevname);
//                 free(tdevname);
//                 sd->alname = Unicode::au(ppDefaultDevice);
//                 sd->name = _tcsdup(tmp);
//             }
//             else
//             {
//                 sd->alname = Unicode::au(pDeviceNames);
//                 sd->name = Unicode::au(pDeviceNames);
//             }
//             _stprintf(tmp, L"OPENAL:%s", sd->alname);
//             sd->cfgname = _tcsdup(tmp);
//         }
//         if (ppDefaultDevice)
//             ppDefaultDevice = nullptr;
//         else
//             pDeviceNames += strlen(pDeviceNames) + 1;
//     }
// }
// #define PORTAUDIO 1
// #if PORTAUDIO
// static void PortAudioEnumerate(struct sound_device* sds)
// {
//     struct sound_device* sd;
//     int num;
//     int i, j;
//     TCHAR tmp[MAX_DPATH], tmp2[MAX_DPATH], * s1, * s2;
//
//     num = Pa_GetDeviceCount();
//     if (num < 0)
//     {
//         TCHAR* errtxt = Unicode::au(Pa_GetErrorText(num));
//         Logger::Write(L"PA: Pa_GetDeviceCount() failed: %08x (%s)\n", num, errtxt);
//         free(errtxt);
//         return;
//     }
//     for (j = 0; j < num; j++)
//     {
//         const PaDeviceInfo* di;
//         const PaHostApiInfo* hai;
//         di = Pa_GetDeviceInfo(j);
//         if (di->maxOutputChannels == 0)
//             continue;
//         hai = Pa_GetHostApiInfo(di->hostApi);
//         if (!hai)
//             continue;
//         if (hai->type == paDirectSound || hai->type == paMME)
//             continue;
//         for (i = 0; i < MAX_SOUND_DEVICES; i++)
//         {
//             sd = &sds[i];
//             if (sd->name == nullptr)
//                 break;
//         }
//         if (i >= MAX_SOUND_DEVICES)
//             return;
//         s1 = Unicode::au(hai->name);
//         s2 = Unicode::au(di->name);
//         _stprintf(tmp, L"[%s] %s", s1, s2);
//         free(s2);
//         free(s1);
//         sd->type = SOUND_DEVICE_PA;
//         sd->name = _tcsdup(tmp);
//         _stprintf(tmp2, L"PORTAUDIO:%s", tmp);
//         sd->cfgname = _tcsdup(tmp2);
//         sd->panum = j;
//     }
// }
// #endif

static LONG WINAPI ExceptionFilter(struct _EXCEPTION_POINTERS* pExceptionPointers, DWORD ec)
{
    return EXCEPTION_EXECUTE_HANDLER;
}

// int force_directsound;

void enumerate_sound_devices()
{
    if (!num_record_devices)
    {
        // HMODULE l = nullptr;
        // Logger::Write(L"Enumerating DirectSound devices..\n");
        // if (os_vista && (sounddrivermask & SOUNDDRIVER_WASAPI))
        //     wasapi_enum(sound_devices);
        // if ((1 || force_directsound || !os_vista) && (sounddrivermask & SOUNDDRIVER_DS))
        // {
        //     DirectSoundEnumerate((LPDSENUMCALLBACK)DSEnumProc, sound_devices);
        // }

        DirectSoundCaptureEnumerate((LPDSENUMCALLBACK)DSEnumProc, record_devices);

        // if (sounddrivermask & SOUNDDRIVER_OPENAL)
        // {
        //     __try {
        //         if (isdllversion(L"openal32.dll", 6, 14, 357, 22))
        //         {
        //             Logger::Write(L"Enumerating OpenAL devices..\n");
        //             if (alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT"))
        //             {
        //                 const char* ppDefaultDevice = alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER);
        //                 const char* pDeviceNames = alcGetString(nullptr, ALC_DEVICE_SPECIFIER);
        //                 if (alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT"))
        //                     pDeviceNames = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER);
        //                 OpenALEnumerate(sound_devices, pDeviceNames, ppDefaultDevice, FALSE);
        //                 #if 0
        //                 ppDefaultDevice = alcGetString(nullptr, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER);
        //                 pDeviceNames = alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER);
        //                 OpenALEnumerate(record_devices, pDeviceNames, ppDefaultDevice, TRUE);
        //                 #endif
        //             }
        //         }
        //     } __except(ExceptionFilter(GetExceptionInformation(), GetExceptionCode()))
        //     {
        //         Logger::Write(L"OpenAL enumeration crashed!\n");
        //         Logger::Flush();
        //     }
        // }
        // #if PORTAUDIO
        // if (sounddrivermask & SOUNDDRIVER_PORTAUDIO)
        // {
        //     __try {
        //         HMODULE hm = WIN32_LoadLibrary(L"portaudio_x86.dll");
        //         if (hm)
        //         {
        //             TCHAR* s;
        //             PaError err;
        //             Logger::Write(L"Enumerating PortAudio devices..\n");
        //             s = Unicode::au(Pa_GetVersionText());
        //             Logger::Write(L"%s (%d)\n", s, Pa_GetVersion());
        //             free(s);
        //             if (Pa_GetVersion() >= 1899)
        //             {
        //                 err = Pa_Initialize();
        //                 if (err == paNoError)
        //                 {
        //                     PortAudioEnumerate(sound_devices);
        //                 }
        //                 else
        //                 {
        //                     s = Unicode::au(Pa_GetErrorText(err));
        //                     Logger::Write(L"Portaudio initialization failed: %d (%s)\n",
        //                               err, s);
        //                     free(s);
        //                     FreeLibrary(hm);
        //                 }
        //             }
        //             else
        //             {
        //                 Logger::Write(L"Too old PortAudio library\n");
        //                 Logger::Flush();
        //                 FreeLibrary(hm);
        //             }
        //         }
        //     } __except(ExceptionFilter(GetExceptionInformation(), GetExceptionCode()))
        //     {
        //         Logger::Write(L"Portaudio enumeration crashed!\n");
        //     }
        // }
        // #endif
        // Logger::Write(L"Enumeration end\n");
        // for (num_sound_devices = 0; num_sound_devices < MAX_SOUND_DEVICES; num_sound_devices++)
        // {
        //     if (sound_devices[num_sound_devices].name == nullptr)
        //         break;
        // }

        for (num_record_devices = 0; num_record_devices < MAX_SOUND_DEVICES; num_record_devices++)
        {
            if (record_devices[num_record_devices].name == nullptr)
                break;
        }
    }
    // return num_sound_devices;
}

// #include <mmdeviceapi.h>
// #include <endpointvolume.h>

/*
   Based on
   http://blogs.msdn.com/larryosterman/archive/2007/03/06/how-do-i-change-the-master-volume-in-windows-vista.aspx
 */

static int setget_master_volume_vista(int setvolume, int* volume, int* mute)
{
    IMMDeviceEnumerator* deviceEnumerator = nullptr;
    IMMDevice* defaultDevice = nullptr;
    IAudioEndpointVolume* endpointVolume = nullptr;
    HRESULT hr;
    int ok = 0;

    hr = CoInitialize(nullptr);
    if (FAILED(hr))
        return 0;
    hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (LPVOID*)&deviceEnumerator);
    if (FAILED(hr))
        return 0;
    hr = deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &defaultDevice);
    if (SUCCEEDED(hr))
    {
        hr = defaultDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, nullptr, (LPVOID*)&endpointVolume);
        if (SUCCEEDED(hr))
        {
            if (setvolume)
            {
                if (SUCCEEDED(endpointVolume->SetMasterVolumeLevelScalar((float)(*volume) / (float)65535.0, nullptr)))
                    ok++;
                if (SUCCEEDED(endpointVolume->SetMute(*mute, nullptr)))
                    ok++;
            }
            else
            {
                float vol;
                if (SUCCEEDED(endpointVolume->GetMasterVolumeLevelScalar(&vol)))
                {
                    ok++;
                    *volume = vol * 65535.0;
                }
                if (SUCCEEDED(endpointVolume->GetMute(mute)))
                {
                    ok++;
                }
            }
            endpointVolume->Release();
        }
        defaultDevice->Release();
    }
    deviceEnumerator->Release();
    CoUninitialize();
    return ok == 2;
}

static void mcierr(const TCHAR* str, DWORD err)
{
    TCHAR es[1000];
    if (err == MMSYSERR_NOERROR)
        return;
    if (mciGetErrorString(err, es, sizeof es / sizeof(TCHAR)))
        Logger::Write(L"MCIErr: %s: %d = '%s'\n", str, err, es);
    else
        Logger::Write(L"%s, errcode=%d\n", str, err);
}
/* from http://www.codeproject.com/audio/mixerSetControlDetails.asp */
static int setget_master_volume_xp(int setvolume, int* volume, int* mute)
{
    MMRESULT result;
    HMIXER hMixer;
    MIXERLINE ml = { 0 };
    MIXERLINECONTROLS mlc = { 0 };
    MIXERCONTROL mc = { 0 };
    MIXERCONTROLDETAILS mcd = { 0 };
    MIXERCONTROLDETAILS_UNSIGNED mcdu = { 0 };
    MIXERCONTROLDETAILS_BOOLEAN mcb = { 0 };
    int ok = 0;

    result = mixerOpen(&hMixer, 0, 0, 0, MIXER_OBJECTF_MIXER);
    if (result == MMSYSERR_NOERROR)
    {
        ml.cbStruct = sizeof(MIXERLINE);
        ml.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
        result = mixerGetLineInfo((HMIXEROBJ)hMixer, &ml, MIXER_GETLINEINFOF_COMPONENTTYPE);
        if (result == MIXERR_INVALLINE)
        {
            ml.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_DIGITAL;
            result = mixerGetLineInfo((HMIXEROBJ)hMixer, &ml, MIXER_GETLINEINFOF_COMPONENTTYPE);
        }
        if (result == MMSYSERR_NOERROR)
        {
            mlc.cbStruct = sizeof(MIXERLINECONTROLS);
            mlc.dwLineID = ml.dwLineID;
            mlc.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
            mlc.cControls = 1;
            mlc.pamxctrl = &mc;
            mlc.cbmxctrl = sizeof(MIXERCONTROL);
            result = mixerGetLineControls((HMIXEROBJ)hMixer, &mlc, MIXER_GETLINECONTROLSF_ONEBYTYPE);
            if (result == MMSYSERR_NOERROR)
            {
                mcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
                mcd.hwndOwner = 0;
                mcd.dwControlID = mc.dwControlID;
                mcd.paDetails = &mcdu;
                mcd.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
                mcd.cChannels = 1;
                mcdu.dwValue = 0;
                if (setvolume)
                {
                    mcdu.dwValue = *volume;
                    result = mixerSetControlDetails((HMIXEROBJ)hMixer, &mcd, MIXER_SETCONTROLDETAILSF_VALUE);
                }
                else
                {
                    result = mixerGetControlDetails((HMIXEROBJ)hMixer, &mcd, MIXER_GETCONTROLDETAILSF_VALUE);
                    *volume = mcdu.dwValue;
                }
                mlc.dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE;
                result = mixerGetLineControls((HMIXEROBJ)hMixer, &mlc, MIXER_GETLINECONTROLSF_ONEBYTYPE);
                if (result == MMSYSERR_NOERROR)
                {
                    mcd.paDetails = &mcb;
                    mcd.dwControlID = mc.dwControlID;
                    mcb.fValue = 0;
                    mcd.cbDetails = sizeof(MIXERCONTROLDETAILS_BOOLEAN);
                    if (setvolume)
                    {
                        mcb.fValue = *mute;
                        result = mixerSetControlDetails((HMIXEROBJ)hMixer, &mcd, MIXER_SETCONTROLDETAILSF_VALUE);
                    }
                    else
                    {
                        result = mixerGetControlDetails((HMIXEROBJ)hMixer, &mcd, MIXER_GETCONTROLDETAILSF_VALUE);
                        *mute = mcb.fValue;
                    }
                    if (result == MMSYSERR_NOERROR)
                        ok = 1;
                }
                else
                    mcierr(L"mixerGetLineControls Mute", result);
            }
            else
                mcierr(L"mixerGetLineControls Volume", result);
        }
        else
            mcierr(L"mixerGetLineInfo", result);
        mixerClose(hMixer);
    }
    else
        mcierr(L"mixerOpen", result);
    return ok;
}

static int set_master_volume(int volume, int mute)
{
    if (os_vista)
        return setget_master_volume_vista(1, &volume, &mute);
    else
        return setget_master_volume_xp(1, &volume, &mute);
}
static int get_master_volume(int* volume, int* mute)
{
    *volume = 0;
    *mute = 0;
    if (os_vista)
        return setget_master_volume_vista(0, volume, mute);
    else
        return setget_master_volume_xp(0, volume, mute);
}

void sound_mute(int newmute)
{
    if (newmute < 0)
        sdp->mute = sdp->mute ? 0 : 1;
    else
        sdp->mute = newmute;
    set_volume(g_curr_conf.sound_volume, sdp->mute);
    config_changed = 1;
}

void sound_volume(int dir)
{
    g_curr_conf.sound_volume -= dir * 10;
    if (g_curr_conf.sound_volume < 0)
        g_curr_conf.sound_volume = 0;
    if (g_curr_conf.sound_volume > 100)
        g_curr_conf.sound_volume = 100;
    g_changed_conf.sound_volume = g_curr_conf.sound_volume;
    set_volume(g_curr_conf.sound_volume, sdp->mute);
    config_changed = 1;
}
void master_sound_volume(int dir)
{
    int vol, mute, r;

    r = get_master_volume(&vol, &mute);
    if (!r)
        return;
    if (dir == 0)
        mute = mute ? 0 : 1;
    vol += dir * (65536 / 10);
    if (vol < 0)
        vol = 0;
    if (vol > 65535)
        vol = 65535;
    set_master_volume(vol, mute);
    config_changed = 1;
}